﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Hiro.ActivationPoints;
using Hiro.ActivationPoints.Interfaces;
using Hiro.Interfaces;
using Hiro.Loaders.Interfaces;
using LinFu.Reflection;

namespace Hiro.Loaders
{
    /// <summary>
    /// Represents a type that can load <see cref="IDependencyMap"/> instances from disk.
    /// </summary>
    public class DependencyMapLoader : IDependencyMapLoader
    {
        private readonly IServiceBuilder _serviceBuilder;
        private readonly IDependencyMapper _dependencyMapper;
        private readonly IActivationPointLoader _activationPointLoader;
        private readonly IAssemblyLoader _assemblyLoader;

        /// <summary>
        /// Initializes a new instance of the DependencyMapLoader class.
        /// </summary>
        public DependencyMapLoader()
            : this(new ConventionOverConfigurationPropertyResolver())
        {
        }

        /// <summary>
        /// Initializes a new instance of the DependencyMapLoader class.
        /// </summary>
        /// <param name="propertyResolver">The property resolver that will determine the dependencies that will be mapped to each property.</param>
        public DependencyMapLoader(IPropertyResolver propertyResolver)
            : this(propertyResolver, new ArgumentResolver())
        {
        }

        /// <summary>
        /// Initializes a new instance of the DependencyMapLoader class.
        /// </summary>
        /// <param name="propertyResolver">The property resolver that will determine the dependencies that will be mapped to each property.</param>
        /// <param name="argumentResolver">The argument resolver that will determine the dependencies that will be mapped to each constructor argument.</param>
        public DependencyMapLoader(IPropertyResolver propertyResolver, IArgumentResolver argumentResolver)
            : this(new ActivationPointLoader(argumentResolver, propertyResolver))
        {
        }

        /// <summary>
        /// Initializes a new instance of the DependencyMapLoader class.
        /// </summary>
        /// <param name="activationPointLoader">The activation point loader that will be used to load a set of activation points from a given file system.</param>
        public DependencyMapLoader(IActivationPointLoader activationPointLoader)
            : this(activationPointLoader, new ServiceBuilder())
        {
        }

        /// <summary>
        /// Initializes a new instance of the DependencyMapLoader class.
        /// </summary>
        /// <param name="activationPointLoader">The activation point loader that will be used to load a set of activation points from a given file system.</param>
        /// <param name="serviceBuilder">The service builder that will be used to convert a service map into a list of services.</param>
        public DependencyMapLoader(IActivationPointLoader activationPointLoader, IServiceBuilder serviceBuilder)
            : this(activationPointLoader, serviceBuilder, new DependencyMapper())
        {

        }

        /// <summary>
        /// Initializes a new instance of the DependencyMapLoader class.
        /// </summary>
        /// <param name="activationPointLoader">The activation point loader that will be used to load a set of activation points from a given file system.</param>
        /// <param name="serviceBuilder">The service builder that will be used to convert a service map into a list of services.</param>
        /// <param name="mapper">The dependency mapper that will sort the list of activation points by dependency type.</param>
        public DependencyMapLoader(IActivationPointLoader activationPointLoader, IServiceBuilder serviceBuilder, IDependencyMapper mapper)
            : this(activationPointLoader, serviceBuilder, mapper, new AssemblyLoader())
        {
        }

        /// <summary>
        /// Initializes a new instance of the DependencyMapLoader class.
        /// </summary>
        /// <param name="activationPointLoader">The activation point loader that will be used to load a set of activation points from a given file system.</param>
        /// <param name="serviceBuilder">The service builder that will be used to convert a service map into a list of services.</param>
        /// <param name="mapper">The dependency mapper that will sort the list of activation points by dependency type.</param>
        /// <param name="assemblyLoader">The assembly loader that will load the list of assemblies from the file system.</param>
        public DependencyMapLoader(IActivationPointLoader activationPointLoader, IServiceBuilder serviceBuilder, IDependencyMapper mapper, IAssemblyLoader assemblyLoader)
        {
            _serviceBuilder = serviceBuilder;
            _dependencyMapper = mapper;
            _activationPointLoader = activationPointLoader;
            _assemblyLoader = assemblyLoader;
        }

        /// <summary>
        /// Gets or sets the value indicating the filter that will be used to determine which services will be compiled into the container.
        /// </summary>
        /// <value>The service filter.</value>
        public Func<IService, bool> ServiceFilter
        {
            get;
            set;
        }

        /// <summary>
        /// Loads a dependency map from disk.
        /// </summary>
        /// <param name="directory">The target directory.</param>
        /// <param name="inputFiles">The wildcard pattern that describes the assemblies that will be read into memory.</param>
        /// <returns>A dependency map.</returns>
        public IDependencyMap LoadFrom(string directory, string inputFiles)
        {
            // Load the list of possible dependencies
            var dependencies = new List<IDependency>();

            var dependencyLoader = new
                CustomLoader<IList<IDependency>>(dependencies, _assemblyLoader, new InterfaceDependencyLoader());

            dependencyLoader.LoadFrom(directory, inputFiles);

            // Load the activation points from the target directory
            var activationPoints = _activationPointLoader.GetActivationPoints(directory, inputFiles);

            // Map the dependencies to the concrete classes
            var serviceMap = _dependencyMapper.GetServiceMap(dependencies, activationPoints);

            // Convert the items in the service map into a list of services
            var services = _serviceBuilder.GetServices(serviceMap);
            var serviceFilter = ServiceFilter ?? (currentService => true);

            // Determine the services that will be inserted into the map
            var selectedServices = from service in services
                                   where serviceFilter(service)
                                   select service;


            var dependencyMap = new DependencyMap();
            foreach (var service in selectedServices)
            {
                var dependency = service.TargetDependency;
                var activationPoint = service.ActivationPoint;

                dependencyMap.AddService(dependency, activationPoint);
            }

            return dependencyMap;
        }
    }
}
